package com.tianji.learning.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.tianji.api.dto.leanring.SignMessage;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;
import static com.tianji.learning.constants.RedisConstants.SIGN_RECORD_KEY_PREFIX;

/**
 * <p>
 * 学习积分记录，每个月底清零 服务实现类
 * </p>
 *
 * @author 东哥
 * @since 2025-07-06
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {
    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;

    /**
     * 1、签到（Day7 - 随堂1）
     *
     * @return
     */
    @Override
    public SignResultVO sign() {
        // 1、签到
        // 1.1、获取签到key
        String key = getSignKey();

        // 1.2、计算offset值
        long offset = LocalDate.now().getDayOfMonth() - 1;

        // 1.3、查询key是否已经存在
        Long keyCount = redisTemplate.countExistingKeys(Collections.singleton(key));
        boolean keyExists = false;
        if(ObjectUtil.isNotNull(keyCount) && keyCount > 0){
            keyExists = true;
        }

        // 1.4、签到 并 校验是否是重复签到（核心）
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if(BooleanUtils.isTrue(exists)){
            throw new BizIllegalException("不允许重复签到!");
        }

        // 1.5、如果key之前是不存在，证明是本月第一次签到，需要设置过期时间
        if(!keyExists){
            LocalDateTime beginTime = LocalDateTime.now();
            LocalDateTime endTime = DateUtils.getMonthEndTime(LocalDate.now());
            Duration duration = DateUtils.between(beginTime, endTime);
            redisTemplate.expire(key,duration);
        }

        // 2、计算连续签到天数(核心)
        int signDays = countSignDays(key);

        // 3、计算签到奖励得分
        int rewardPoints = computeRewardPoints(signDays);

        // 4、构建vo
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);

        // 5、发送保存积分明细的消息（埋点发消息）
        try {
            mqHelper.send(LEARNING_EXCHANGE, SIGN_IN, SignMessage.of(UserContext.getUser(),vo.totalPoints()));
        } catch (Exception e) {
            log.error("发送保存积分明细消息、失败：{}", e);
        }

        // 6、返回vo
        return vo;
    }

    /**
     * 2、查询签到记录（Day7 - 随堂2）
     *
     * @return
     */
    @Override
    public Byte[] querySignRecords() {
        // 1、获取签到key
        String key = getSignKey();

        // 2、通过bitfield命令获取截止到当天签到结果的十进制（bitfield sign:uid:2:202507 get u当前属于当月第几天 0）
        int dayOfMonth = LocalDate.now().getDayOfMonth();
        List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if(ObjectUtil.isEmpty(result)){
            return new Byte[]{};
        }
        long signResult = result.get(0);


        // 3、循环每一天的签到记录，赋值给数组
        Byte[] arr = new Byte[dayOfMonth];
        int pos = dayOfMonth - 1;
        while (pos >= 0) {
            arr[pos--] = (byte) (signResult & 1);
            signResult >>>= 1;
        }
        return arr;
    }

    /**
     * 计算签到奖励得分
     * @param signDays 连续签到天数
     * @return
     */
    private int computeRewardPoints(int signDays) {
        switch (signDays) {
            case 7:
                return 10;
            case 14:
                return 20;
            case 28:
                return 40;
            default:
                return 0;
        }
    }

    /**
     * 计算连续签到天数
     * @param key
     * @return
     */
    private int countSignDays(String key) {
        // 1、通过bitfield命令获取截止到当天签到结果的十进制（bitfield sign:uid:2:202507 get u当前属于当月第几天 0）
        int dayOfMonth = LocalDate.now().getDayOfMonth();
        List<Long> result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if(ObjectUtil.isEmpty(result)){
            return 0;
        }
        long signResult = result.get(0);

        // 2、定义一个计数器，用来记录连续签到天数
        int days = 0;

        // 3、用签到结果不断跟1做与运算，判断是否等于1，如果等于1，则说明是签到了，结果等于0，则说明没有签到，结束循环
        while ((signResult & 1) == 1) {
            // 3.1、连续签到天数 +1
            days++;
            // 3.2、签到结果无符号右移一位
            signResult >>>= 1;
        }
        return days;
    }

    /**
     * 获取当前用户当前月的签到key（sign:uid:2:202507）
     * @return
     */
    private String getSignKey() {
        // 1、获取当前登录用户ID
        Long userId = UserContext.getUser();

        // 2、获取当前日期
        String dateStr = LocalDate.now().format(DateUtils.DATE_SUFFIX_FORMATTER);   // :202507

        // 3、拼接key并返回
        return SIGN_RECORD_KEY_PREFIX + userId + dateStr;
    }
}
